package com.liyuci.core.aspect;

import com.liyuci.core.entity.RestPageResult;
import com.liyuci.core.entity.RestResult;
import com.liyuci.core.helper.BlankHelper;
import com.liyuci.core.helper.IPAddressHelper;
import com.liyuci.core.helper.StringsHelp;
import com.liyuci.core.service.IService;
import com.liyuci.eleon.syslog.SysLog;

import io.swagger.annotations.ApiOperation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.event.Level;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 资源访问日志拦截器
 * @author liyuci
 * @date 2018-02-04 11:02:04
 */
@Slf4j
@Configuration
@Aspect
public class ResourceAccessLogConfig {

	@Autowired
	private IService<SysLog> sysLogService;
	
	@Around("within(@org.springframework.stereotype.Controller *) && @annotation(apiOperation)")
	public Object access(final ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable
	{
		StopWatch clock = new StopWatch();
        clock.start();
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Exception e = null;
		Object obj = null;
		try
		{
			obj = joinPoint.proceed();
		}
		catch(Exception ex)
		{
			/**
			 * 这里还可以改进，如果抛出的异常是自定义的异常，可以获取其异常信息作为提示的信息
			 */
			
			//出现错误，判断Controller的返回值类型，如果是String就默认返回出错页面即可
			//如果是RestResult或者RestPageResult类型的使用其调用failure()方法替换Controller的返回值
			e = ex;
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
			Class<?> returnType = signature.getReturnType();
			//不为String时，表示返回的是Json数据信息
			if(returnType.getSimpleName().equals(RestResult.class.getSimpleName()))
			{
				obj = RestResult.failure();
			}
			else if(returnType.getSimpleName().equals(RestPageResult.class.getSimpleName()))
			{
				obj = RestPageResult.failure();
			}
			else
			{
				Throwable error = (Throwable)request.getAttribute("javax.servlet.error.exception");
				if(BlankHelper.isNotBlank(error))
				{
					while(error instanceof ServletException && error.getCause() != null)
					{
						error = ((ServletException)error).getCause();
					}
				}
				else
				{
					error = ex;
				}
				request.setAttribute("cause", error);
				throw ex;
			}
		}
		finally {
			clock.stop();
			saveAccessSysLog(joinPoint, apiOperation, request, clock, obj, e);
		}
		return obj;
	}
	
	@Around("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(apiOperation)")
	public Object restAccess(final ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable
	{
		return access(joinPoint, apiOperation);
	}
	
	/**
	 * 保存http请求的信息
	 * @author liyuci
	 * @date 2018-02-04 14:25:48
	 * @param joinPoint
	 * @param apiOperation
	 * @param request
	 * @param startTime
	 * @param endTime
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	private void saveAccessSysLog(final ProceedingJoinPoint joinPoint, ApiOperation apiOperation,
			HttpServletRequest request, StopWatch clock, Object result, Exception e) throws Exception
	{
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
		String msg = "";
		if(null != e)
		{
			msg = e.toString();
			if(null != e.getStackTrace() && e.getStackTrace().length > 0)
			{
				msg += "::" + e.getStackTrace()[0].toString();
			}
		}
		
		SysLog sysLog = new SysLog();
		sysLog.setName(apiOperation.value()); 
		sysLog.setClassName(signature.getDeclaringTypeName());
		sysLog.setMethodName(signature.getName());
		sysLog.setParamValues(Arrays.asList(joinPoint.getArgs()).toString());
		sysLog.setRequestMethod(request.getMethod());
		List<Class> listParamType = Arrays.asList(signature.getParameterTypes());
		List<String> listParamName = new ArrayList<>();
		if(BlankHelper.isNotBlank(signature.getParameterNames()))
		{
			listParamName = Arrays.asList(signature.getParameterNames());
		}
		if(null == e)
		{
			sysLog.setLogLevel(Level.INFO.toString());
			sysLog.setReturnValue(result.toString());
		}
		else
		{
			sysLog.setLogLevel(Level.ERROR.toString());
			sysLog.setExceptionMsg(msg);
		}
		int size = listParamType.size();
		StringBuilder methodNameBuilder = new StringBuilder();
		for(int i = 0; i < size; i++)
		{
			String paramType = listParamType.get(i).getSimpleName();
			methodNameBuilder.append(paramType);
			//参数名称为空，在报错的情况下会出现
			if(!listParamName.isEmpty())
			{
				methodNameBuilder.append(" " + listParamName.get(i) + ", ");
			}
			else
			{
				methodNameBuilder.append(" " + StringsHelp.toLowerFristChar(paramType) + ", ");
			}
		}
		sysLog.setParamNames(StringsHelp.removeStringFromLast(methodNameBuilder.toString(), 2));
		sysLog.setIpAddress(IPAddressHelper.getIpAddress(request));
		sysLog.setTakeTime(clock.getTime());
		sysLog.setHeadUserAgent(request.getHeader("User-Agent"));
		sysLog.setHeadCookie(request.getHeader("Cookie"));
		sysLog.setHeadReferer(request.getHeader("Referer"));
		sysLog.setRequestUrl(request.getRequestURL().toString());
		sysLog.setRequestProtocol(request.getProtocol());
		sysLog.setReturnType(signature.getReturnType().getSimpleName());
		//用户标识信息
		sysLog.setUserIdentity(null);
		this.sysLogService.saveOrUpdate(sysLog);
		if(null == e)
		{
			log.info("【"+ sysLog.getName() +"::"+ signature.getDeclaringType().getSimpleName() +"."+ sysLog.getMethodName() +"】[耗时"+ sysLog.getTakeTime() +"ms]");
		}
	}
	
}
